from json import JSONDecodeError

import allure
from requests.cookies import RequestsCookieJar

from handlers.handler_files.handler_yaml import HandlerYaml
from handlers.handler_log import logger
from handlers.handler_mysql import HandlerMysql
from handlers.handler_basics import is_none_dict, object_to_str
from handlers.handler_assert.equal_assertion import equal_assertion
from handlers.handler_assert.mysql_assertion import mysql_assertion
import requests


class HandlerResponse:
    def __init__(self):
        self.handler_yaml = HandlerYaml()

    @staticmethod
    def response_result_extract_nodes(response: requests.Response, *nodes) -> int | str | dict:
        """
        获取 json格式的响应数据 的指定 节点数据,
        :param response: 请求响应对象
        :param nodes: 指定节点
        :return: int、str、dict
        """
        response_data = None
        try:
            # 响应数据
            if response.text != '':
                response_data = response.json()
            else:
                response_data = None

            for node in nodes:
                # 如果查询类容为状态码，就直接返回
                if node == 'status_code':
                    response_data = response.status_code
                # 如果查询的为节点为字符串
                elif isinstance(response_data, dict) and node in response_data:
                    response_data = response_data[node]
                # 如果查询的节点为数字
                elif isinstance(node, int) and node < len(response_data):
                    response_data = response_data[node]
                # 没有查询到对应的nodes节点数据
                else:
                    response_data = None
        except JSONDecodeError as e:
            logger.warning("json格式转换异常", e)
        except Exception as e:
            logger.exception(e)
        finally:
            return response_data

    def response_all_assert(self, assert_data: dict, response: requests.Response) -> bool:
        """
        执行全部断言 响应结果符合预期
        :param assert_data: 需要断言的数据
        :param response: 响应结果
        :return: 断言是否全部断言成功， 全部成功为True 否则为 False
        """
        result = 0

        try:
            allure.attach(name="断言数据", body=object_to_str(assert_data), attachment_type=allure.attachment_type.JSON)
            allure.attach(name="响应数据", body=object_to_str(response.json()), attachment_type=allure.attachment_type.JSON)
            logger.info(f"断言数据：'{assert_data}'")
            logger.info(f"响应数据：'{response.text}'")

            if is_none_dict(assert_data):
                # 开始断言了
                for assertion_type, assertion_data in assert_data.items():
                    # key 表示被断言的对象， value表示被断言对象预期值
                    for key, value in assertion_data.items():
                        # 断言开始  key 被断言的对象， assertion_type 断言种类  response 实际结果  value预期结果
                        result = self.response_assert(key, assertion_type, response, value) + result
            else:
                logger.error(f'解析异常，请输入正确的断言格式，现在的断言格式为：{assert_data}')
        except Exception as e:
            logger.exception(f'断言 预期结果 响应结果 出现异常情况：{e}')
        finally:
            return result

    def response_assert(self, key, assert_type: str, response: requests.Response, expected_result) -> int:
        """
        执行一种断言
        :param key: 被断言的对象
        :param assert_type: 断言类型
        :param response: 实际结果
        :param expected_result: 预期结果
        :return:
        """
        result = 0
        try:
            assert_types = ['eq', 'contain', 'neq', 'db']
            # 只要传入的断言在断言类型中就可以进行断言
            if assert_type in assert_types:

                # 相等断言
                if assert_type == 'eq':
                    # 获取实际测试结果数据
                    actual_result = self.response_result_extract_nodes(response, key)
                    # 开始断言
                    result = equal_assertion(key, actual_result, expected_result)

                # 数据库断言
                elif assert_type == 'db':
                    # 开始断言
                    result = mysql_assertion(key, HandlerMysql(), expected_result)

            else:
                logger.error(f'当前没有这样的{assert_type}断言，如果需要请自己在代码中添加这种断言')
                return False
        except Exception as e:
            logger.error(e)
        finally:
            return result

    def response_result_extract_dict(self, response: requests.Response, extract_datas: dict) -> int:
        """
        响应数据提取到session.yaml文件中
        :param response: 响应数据
        :param extract_datas: 接口的extract数据
        :return: False 提取类容失败， True 提取内容成功
        """
        # 提取失败次数
        extract_failed_numbers = 0
        try:
            if not is_none_dict(extract_datas):
                logger.error(f'请输入正确格式的yaml文件进行数据提取操作！格式为,存储节点名: 主节点.子节点.本节点')
                extract_failed_numbers = 1 + extract_failed_numbers

            # stored_node_key 需要获取的存储节点名字， stored_node_position 节点在json中的结构
            for stored_node_key, stored_node_position in extract_datas.items():
                # 获取节点位置
                stored_node_position = stored_node_position.split('.')
                # 判断是否需要存储cookies
                if stored_node_position[0] == 'cookies':
                    # 将cookie在yaml的结构创建好
                    stored_node_data = {stored_node_key: {}}
                    # 从响应结果中获取cookies信息
                    cookies: RequestsCookieJar = response.cookies
                    # 遍历所有cookie,将所有cookie存入dict_result对象中
                    for cookie_key, cookie_value in cookies.items():
                        stored_node_data[stored_node_key].update({cookie_key: cookie_value})
                else:
                    # 从响应结果中获取存储节点的数据
                    stored_node_data = self.response_result_extract_nodes(response, *stored_node_position)
                    stored_node_data = {stored_node_key: stored_node_data}

                # dict_result对象追加吸入到session.yaml中去
                if self.handler_yaml.insert_extracts_yaml(stored_node_data):
                    logger.success(f'{stored_node_key}节点数据存储成功')
                else:
                    extract_failed_numbers = 1 + extract_failed_numbers

        except Exception as e:
            logger.exception(f'响应数据提取到session.yaml文件中 异常：{e}')
            return False
        finally:
            return extract_failed_numbers
